package auth.config;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.util.CollectionUtils;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.provisioning.JdbcUserDetailsManager.*;

@RequiredArgsConstructor
@Configuration(proxyBeanMethods = false)
class WebSecurityConfig {
    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, JdbcTemplate jdbcTemplate) throws Exception {
        UserDetailsService userDetailsService = userDetailsService(jdbcTemplate);
        http.authorizeHttpRequests(authorizeRequests -> authorizeRequests
            .antMatchers("/**/*.ico", "/**/*.css", "/**/*.css.map").permitAll()
            .anyRequest().authenticated()
        ).userDetailsService(userDetailsService).formLogin(withDefaults());
        http.cors(withDefaults()).csrf(AbstractHttpConfigurer::disable); // Fetch/XHR mode login/logout
        http.sessionManagement(sessionManagementCustomizer -> sessionManagementCustomizer
            .maximumSessions(1) // .maxSessionsPreventsLogin(true)
        );
        // http.rememberMe(rememberMeCustomizer -> rememberMeCustomizer
        //     .key("jhxxb")
        //     .userDetailsService(userDetailsService)
        //     .tokenRepository(tokenRepository(jdbcTemplate))
        // );
        return http.build();
    }

    UserDetailsService userDetailsService(JdbcTemplate jdbcTemplate) {
        JdbcUserDetailsManager userDetailsService = new JdbcUserDetailsManager() {
            @Override
            protected void addCustomAuthorities(String username, List<GrantedAuthority> authorities) {
                // authorities.addAll(getJdbcTemplate().queryForList(
                //         "SELECT DISTINCT authority FROM menu WHERE id IN (" +
                //             "  SELECT menu_id FROM group_menu WHERE group_id IN (" +
                //             "    SELECT group_id FROM group_members WHERE username = ?" +
                //             ")) AND authority IS NOT NULL"
                //         , String.class, username).stream().map(SimpleGrantedAuthority::new)
                //     .collect(Collectors.toList())
                // );
                authorities.addAll(getJdbcTemplate().queryForList(
                        "SELECT DISTINCT group_name FROM `groups` WHERE id IN (" +
                            "  SELECT group_id FROM group_members WHERE username = ?" +
                            ") AND group_name IS NOT NULL"
                        , String.class, username).stream().map("ROLE_"::concat).map(SimpleGrantedAuthority::new)
                    .collect(Collectors.toList())
                );
                if (CollectionUtils.isEmpty(authorities)) authorities.add(new SimpleGrantedAuthority("login"));
            }
        };
        userDetailsService.setJdbcTemplate(jdbcTemplate);
        userDetailsService.setEnableGroups(true);
        userDetailsService.setFindAllGroupsSql(DEF_FIND_GROUPS_SQL.replace("groups", "`groups`"));
        userDetailsService.setFindUsersInGroupSql(DEF_FIND_USERS_IN_GROUP_SQL.replace("groups", "`groups`"));
        userDetailsService.setInsertGroupSql(DEF_INSERT_GROUP_SQL.replace("groups", "`groups`"));
        userDetailsService.setFindGroupIdSql(DEF_FIND_GROUP_ID_SQL.replace("groups", "`groups`"));
        userDetailsService.setDeleteGroupSql(DEF_DELETE_GROUP_SQL.replace("groups", "`groups`"));
        userDetailsService.setRenameGroupSql(DEF_RENAME_GROUP_SQL.replace("groups", "`groups`"));
        userDetailsService.setGroupAuthoritiesSql(DEF_GROUP_AUTHORITIES_QUERY_SQL.replace("groups", "`groups`"));
        userDetailsService.setGroupAuthoritiesByUsernameQuery(DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY.replace("groups", "`groups`"));
        userDetailsService.setUsersByUsernameQuery(DEF_USERS_BY_USERNAME_QUERY.replace("enabled", "enabled,account_non_locked,account_non_expired,credentials_non_expired"));
        userDetailsService.afterPropertiesSet();
        return userDetailsService;
    }

    PersistentTokenRepository tokenRepository(JdbcTemplate jdbcTemplate) {
        // https://docs.spring.io/spring-security/reference/servlet/authentication/rememberme.html#remember-me-persistent-token
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setJdbcTemplate(jdbcTemplate);
        jdbcTokenRepository.afterPropertiesSet();
        return jdbcTokenRepository;
    }

    @Bean
    public SessionRegistry sessionRegistry() { // OpenID Connect 1.0 Logout 需要
        // https://docs.spring.io/spring-authorization-server/docs/current/reference/html/core-model-components.html#session-registry
        return new SessionRegistryImpl();
    }

    @Bean
    HttpSessionEventPublisher httpSessionEventPublisher() { // .maxSessionsPreventsLogin(true) 和 OpenID Connect 1.0 Logout 需要
        // https://docs.spring.io/spring-security/reference/servlet/authentication/session-management.html#ns-concurrent-sessions
        return new HttpSessionEventPublisher();
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        // https://docs.spring.io/spring-security/reference/servlet/integrations/cors.html
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowCredentials(true);
        configuration.setAllowedOriginPatterns(Collections.singletonList("*"));
        configuration.applyPermitDefaultValues();
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}